package query_understanding.query_language.doc_entity_tree;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.stanford.nlp.ling.IndexedWord;
import edu.stanford.nlp.semgraph.SemanticGraphEdge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import query_understanding.common.nlp_toolkit.RelationEdge;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by julianzliu on 4/22/2017.
 */
public class EntityQueryTree {

    static final Logger logger = LoggerFactory.getLogger(EntityQueryTree.class);


    public String query;

    public Double treeScore;

    public Boolean finished;

    public Boolean growthed; //是否有增长

    //public QueryNodeTokenMap curret;

    public Set<IndexedWord> visited;

    public Set<IndexedWord> unVisited;

    public Set<IndexedWord> allWords;

    public Set<SemanticGraphEdge> visitedEdges;

    public Map<IndexedWord, QueryNodeTokenMap> nodeInvertMap;

    public Map<IndexedWord, QueryNodeTokenMap> edgeInvertMap;

    public Map<QueryElement.AbstractQueryElement, QueryNodeTokenMap> eleTokenMap;




    // 深拷贝
    public QueryNode.BaseNode topicEntity;
    // 深拷贝
    public QueryNode.BaseNode answerEntity;




    private ObjectMapper mapper;

    public EntityQueryTree(){

        this.unVisited = new HashSet<>();

        this.visited = new HashSet<>();

        this.visitedEdges  = new HashSet<>();

        this.allWords = new HashSet<>();

        this.nodeInvertMap = new HashMap<>();

        this.edgeInvertMap = new HashMap<>();

        this.eleTokenMap = new HashMap<>();

        this.finished = false;

        this.growthed = false;
    }

    /*
    复制构造函数
     */
    public EntityQueryTree(EntityQueryTree another_tree){
        this.growthed = false;
        this.finished = false;

        this.treeScore = another_tree.treeScore;
        //this.curret = another_tree.curret;
        this.visited = new HashSet<>(another_tree.visited);
        this.unVisited = new HashSet<>(another_tree.unVisited);
        this.allWords = another_tree.allWords;

        /*
        TODO：  错误源，，树之间交叉了
         */
        //this.nodeInvertMap = another_tree.nodeInvertMap;
        this.nodeInvertMap = new HashMap<>();

        this.edgeInvertMap = new HashMap<>();

        this.eleTokenMap = new HashMap<>();

        this.visitedEdges = new HashSet<>( another_tree.visitedEdges );

        /*
        this.topicEntity = new QueryNode.EntityNode((QueryNode.EntityNode)another_tree.topicEntity);
        // TODO
        if(another_tree.answerEntity != null)
            this.answerEntity = new QueryNode.BaseNode( another_tree.answerEntity );
        else this.answerEntity = null;
        */
    }


    public void addRelation(RelationEdge.BaseRelation relation){
        System.out.println("[addRelation] relation = " + relation.toString());
        this.visitedEdges.add( relation.inEdge );
        this.visitedEdges.add( relation.outEdge );
    }


    public void addIndexWord(IndexedWord token, QueryElement.AbstractQueryElement ele){
        this.visited.add( token);
        if(this.unVisited.contains( token )){
            this.unVisited.remove(token);
        }
        QueryNodeTokenMap tokenMap = new QueryNodeTokenMap();
        tokenMap.mainToken = token;
        tokenMap.tokenSet.add(token);
        tokenMap.queryElement = ele;
        if(ele.elementType == QueryElement.ElementType.NODE_TYPE)
            this.addQueryNodeTokenMapOfNode(tokenMap);
        else
            this.addQueryNodeTokenMapOfEdge(tokenMap);
    }

    public void addQueryNodeTokenMapOfNode(QueryNodeTokenMap tokenMap){
        this.growthed = true;
        this.nodeInvertMap.put(tokenMap.mainToken, tokenMap);
        for(IndexedWord word: tokenMap.tokenSet){
            this.nodeInvertMap.put(word, tokenMap);
        }
        this.visited.add(tokenMap.mainToken);
        if(this.unVisited.contains(tokenMap.mainToken))
            this.unVisited.remove(tokenMap.mainToken);

        this.visited.addAll( tokenMap.tokenSet );
        for(IndexedWord token:tokenMap.tokenSet){
            if(this.unVisited.contains(token)){
                this.unVisited.remove(token);
            }
        }

        this.eleTokenMap.put( tokenMap.queryElement, tokenMap );
    }

    public void addQueryNodeTokenMapOfEdge(QueryNodeTokenMap tokenMap){
        this.growthed = true;
        this.edgeInvertMap.put(tokenMap.mainToken, tokenMap);
        for(IndexedWord word: tokenMap.tokenSet){
            this.edgeInvertMap.put(word, tokenMap);
        }
        this.visited.add(tokenMap.mainToken);
        if(this.unVisited.contains(tokenMap.mainToken))
            this.unVisited.remove(tokenMap.mainToken);

        this.visited.addAll( tokenMap.tokenSet );
        for(IndexedWord token:tokenMap.tokenSet){
            if(this.unVisited.contains(token)){
                this.unVisited.remove(token);
            }
        }

        this.eleTokenMap.put( tokenMap.queryElement, tokenMap );
    }

    public Boolean generateFinished(){
        String str = "[CHECK Whether Tree generateFinished] [size = " + this.unVisited.size() + "] ";
        for(IndexedWord v : this.unVisited)
            str += " [" + v.word() + " : " + v.tag() + " ] ";
        System.out.println(str);
        if(this.unVisited.size() == 0)
            return true;
        return false;
    }






    public EntityQueryTree deepCopyTree(){
        EntityQueryTree new_tree = new EntityQueryTree( this );

        Set<QueryNode.BaseNode> visitedNodes = new HashSet<>();
        Set<QueryEdge.BaseEdge> visitedEdges = new HashSet<>();
        Map<QueryElement.AbstractQueryElement, QueryElement.AbstractQueryElement> eleMap = new HashMap<>();

        QueryNode.BaseNode root = this.topicEntity;

        if(root==null){
            System.out.println("This is An Empty Tree!");
            return null;
        }

        QueryNode.BaseNode new_root = recurrNode(root, visitedNodes, visitedEdges, eleMap);

        new_tree.topicEntity = new_root;

        if(this.answerEntity != null)
            new_tree.answerEntity = (QueryNode.BaseNode)eleMap.get(this.answerEntity);
        else
            new_tree.answerEntity = null;

        recoverInvertMap(new_tree, this, eleMap);

        return new_tree;
    }

    private QueryNode.BaseNode recurrNode(QueryNode.BaseNode root,Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges, Map<QueryElement.AbstractQueryElement, QueryElement.AbstractQueryElement> eleMap){
        QueryNode.BaseNode new_root = null;
        if(root.nodeType == QueryNode.NodeType.ENTITY){
            new_root = ((QueryNode.EntityNode)root).copy();
        }
        if(root.nodeType == QueryNode.NodeType.VARIABLE){
            new_root = ((QueryNode.VariableNode)root).copy();
        }
        if(root.nodeType == QueryNode.NodeType.CONCEPT){
            new_root = ((QueryNode.ConceptNode)root).copy();
        }
        if(root.nodeType == QueryNode.NodeType.KEYWORD){
            new_root = ((QueryNode.KeywordNode)root).copy();
        }
        eleMap.put(root, new_root);
        visitedNodes.add( root );
        for(QueryEdge.BaseEdge edge: root.edgesSet ){
            if(visitedEdges.contains( edge )) continue;;
            QueryEdge.BaseEdge new_edge = recurrEdge(edge, visitedNodes, visitedEdges, eleMap);
            new_root.edgesSet.add( new_edge );
            new_edge.nodesSet.add( new_root );
        }
        return new_root;
    }

    private QueryEdge.BaseEdge recurrEdge(QueryEdge.BaseEdge edge, Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges, Map<QueryElement.AbstractQueryElement, QueryElement.AbstractQueryElement> eleMap){
        QueryEdge.BaseEdge new_edge = null;
        if(edge.edgeType == QueryEdge.EdgeType.YAGO_EDGE){
            new_edge = ((QueryEdge.YagoEdge)edge).copy();
        }
        if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE){
            new_edge = ((QueryEdge.OccurEdge)edge).copy();
        }
        if(edge.edgeType == QueryEdge.EdgeType.TYPE_EDGE){
            new_edge = ((QueryEdge.TypeEdge)edge).copy();
        }
        eleMap.put(edge, new_edge);
        visitedEdges.add(edge);
        for(QueryNode.BaseNode root: edge.nodesSet ){
            if(visitedNodes.contains(root)) continue;
            QueryNode.BaseNode new_root = recurrNode(root, visitedNodes, visitedEdges, eleMap);
            new_edge.nodesSet.add(new_root);
            new_root.edgesSet.add(new_edge);
        }
        return new_edge;
    }

    private void recoverInvertMap(EntityQueryTree new_tree, EntityQueryTree old_tree, Map<QueryElement.AbstractQueryElement, QueryElement.AbstractQueryElement> eleMap ){
        for(IndexedWord word: old_tree.nodeInvertMap.keySet()){
            QueryNodeTokenMap tokenMap = old_tree.nodeInvertMap.get(word);
            QueryNodeTokenMap newTokenMap = new QueryNodeTokenMap( tokenMap );
            newTokenMap.queryElement = eleMap.get(tokenMap.queryElement);
            new_tree.nodeInvertMap.put(word, newTokenMap);
            new_tree.eleTokenMap.put(newTokenMap.queryElement, newTokenMap);
        }
        for(IndexedWord word: old_tree.edgeInvertMap.keySet()) {
            QueryNodeTokenMap tokenMap = old_tree.edgeInvertMap.get( word );
            QueryNodeTokenMap newTokenMap = new QueryNodeTokenMap( tokenMap );
            newTokenMap.queryElement = eleMap.get(tokenMap.queryElement);
            new_tree.edgeInvertMap.put(word, newTokenMap);
            new_tree.eleTokenMap.put(newTokenMap.queryElement, newTokenMap);
        }
    }


    /****************************************************
     * 生成有向的树
     * @param root
     * @return
     */
    public JsonNode genDirectedJsonTree(QueryNode.BaseNode root){

        this.mapper = new ObjectMapper();

        Set<QueryNode.BaseNode> visitedNodes = new HashSet<>();
        Set<QueryEdge.BaseEdge> visitedEdges = new HashSet<>();

        String indent = "  ";

        JsonNode rootNode = recurrentNode( root, indent, visitedNodes, visitedEdges);

        return rootNode;

    }


    private  JsonNode recurrentNode(QueryNode.BaseNode root, String indent, Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges){
        //String printStr = "[" + root.nodeType.toString() + "]";
        ObjectNode rootNode = this.mapper.createObjectNode();
        rootNode.put("hash", root.hashCode() );
        ArrayNode edges = this.mapper.createArrayNode();
        rootNode.put("outEs", edges);

        rootNode.put( "type", root.nodeType.toString() );
        if(root.nodeType == QueryNode.NodeType.ENTITY ){
            rootNode.put("yago_id", ((QueryNode.EntityNode)root).yago_id);
        }
        if( root.nodeType == QueryNode.NodeType.CONCEPT){
            rootNode.put("yago_id", ((QueryNode.EntityNode)root).yago_id);
        }

        //System.out.println(indent + printStr);
        visitedNodes.add( root );
        for(QueryEdge.BaseEdge edge: root.edgesSet ){
            if(visitedEdges.contains( edge )){
                rootNode.put("inE", edge.hashCode() );
                //continue;
            }else{
                //root.outE.add( edge );
                JsonNode edgeNode = recurrentEdge( edge, indent + "  ", visitedEdges, visitedNodes);
                edges.add( edgeNode );
            }
        }
        return rootNode;
    }


    private  JsonNode recurrentEdge(QueryEdge.BaseEdge edge, String indent, Set<QueryEdge.BaseEdge> visitedEdges, Set<QueryNode.BaseNode> visitedNodes){
        //String printStr = "<" + edge.edgeType.toString() + ">";
        ObjectNode edgeNode = this.mapper.createObjectNode();
        edgeNode.put("hash", edge.hashCode());
        //ObjectNode nodes = this.mapper.createArrayNode();
        //edgeNode.put("nodes", nodes);

        edgeNode.put("type", edge.edgeType.toString() );

        if(edge.edgeType == QueryEdge.EdgeType.YAGO_EDGE ){
            //printStr += " " + ((QueryEdge.YagoEdge)edge).yago_label;
            edgeNode.put("yago_label", ((QueryEdge.YagoEdge)edge).yago_label );
        }

        if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE){
            //printStr += " " + ((QueryEdge.OccurEdge)edge).text;
            edgeNode.put("text", ((QueryEdge.OccurEdge)edge).text );
        }

        //System.out.println( indent + printStr );

        visitedEdges.add( edge );
        for(QueryNode.BaseNode root: edge.nodesSet ){
            if(visitedNodes.contains(root)){
                edgeNode.put("outV", root.hashCode() );
            }
            else {
                JsonNode inNode = recurrentNode(root, indent + " ", visitedNodes, visitedEdges);
                edgeNode.put("inV", inNode);
            }
        }
        return edgeNode;
    }


    /*******************************************************
     *
     */
    public void markAnswerNode(){
        this.answerEntity = this.topicEntity; //init
        Set<QueryNode.BaseNode> visitedNodes = new HashSet<>();
        Set<QueryEdge.BaseEdge> visitedEdges = new HashSet<>();

        recurrentMarkNode(this.topicEntity,  visitedNodes, visitedEdges);
    }


    private  void recurrentMarkNode(QueryNode.BaseNode root, Set<QueryNode.BaseNode> visitedNodes, Set<QueryEdge.BaseEdge> visitedEdges){
        if(root.nodeType == QueryNode.NodeType.VARIABLE ){
            IndexedWord curr_token  = (this.eleTokenMap.get( root ).mainToken);
            IndexedWord answ_token = (this.eleTokenMap.get( this.answerEntity ).mainToken);
            System.out.println("[markAnswerNode] curr_begin = " + curr_token.beginPosition() + " , answ_begin = " + answ_token.beginPosition() );
            if(curr_token.beginPosition() < answ_token.beginPosition() || this.answerEntity.nodeType != QueryNode.NodeType.VARIABLE){
                this.answerEntity = root;
                logger.info("[Mark Answer Node] , answer = {}", this.answerEntity.hashCode() );
                System.out.println("[markAnswerNode] update Answer Node, this.answerNode = " + this.answerEntity.hashCode() );
            }
        }
        visitedNodes.add( root );
        for(QueryEdge.BaseEdge edge: root.edgesSet ){
            if(visitedEdges.contains( edge )){
                //root.inE = edge;
                continue;
            }else{
                //root.outE.add( edge );
                recurrentMarkEdge( edge, visitedEdges, visitedNodes);
            }
        }
    }


    private  void recurrentMarkEdge(QueryEdge.BaseEdge edge, Set<QueryEdge.BaseEdge> visitedEdges, Set<QueryNode.BaseNode> visitedNodes){
        visitedEdges.add( edge );
        for(QueryNode.BaseNode root: edge.nodesSet ){
            if(visitedNodes.contains(root)){
                //edge.out = root;
                continue;
            }
            else {
                //edge.in = root;
                recurrentMarkNode(root, visitedNodes, visitedEdges);
            }
        }
    }













    /*************************************************************************
     *
     * @param args
     */
    public static void main(String[] args){

    }


}
